//
// qemu ... -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
//

#include "error.h"
#include "type.h"
#include "stdio.h"
#include "mmu.h"
#include "mmulayout.h"
#include "lock.h"
#include "proc.h"
#include "string.h"
#include "dbg.h"

#include "fs/disk.h"

#ifdef QEMU

#include "fs/virtio.h"

#define REGISTER(r) (*(volatile uint32_t*)(VIRTIO0_V + (r)))

char buf_temp[512];

static __attribute__((aligned(PAGE_SIZE))) struct disk_t {
    char pages[2 * PAGE_SIZE];
    VRingDesc* desc;
    uint16_t* avail;
    UsedArea* used;
    char free[VIRTIO_NUM];
    uint16_t used_idx;

    struct virt_info_t {
        uint8_t* buf;
        char status;
        int disk;
    } virt_info[VIRTIO_NUM];

    // semaphore_t vdisk_lk;
    spinlock_t vdisk_lk;
} disk;

void
virtio_disk_init(void) {  // TODO 如何读写的？
    uint32_t status = 0;
    spinlock_init(&disk.vdisk_lk, "virtio_disk_lk");

    if ((uint32_t)REGISTER(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 || (uint32_t)REGISTER(VIRTIO_MMIO_VERSION) != 1 ||
        (uint32_t)REGISTER(VIRTIO_MMIO_DEVICE_ID) != 2 || (uint32_t)REGISTER(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551)
        panic("could not find virtio disk");

    status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
    REGISTER(VIRTIO_MMIO_STATUS) = status;
    status |= VIRTIO_CONFIG_S_DRIVER;
    REGISTER(VIRTIO_MMIO_STATUS) = status;

    uint64_t feature = REGISTER(VIRTIO_MMIO_DEVICE_FEATURES);
    feature &= ~(1 << VIRTIO_BLK_F_RO);
    feature &= ~(1 << VIRTIO_BLK_F_SCSI);
    feature &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE);
    feature &= ~(1 << VIRTIO_BLK_F_MQ);
    feature &= ~(1 << VIRTIO_F_ANY_LAYOUT);
    feature &= ~(1 << VIRTIO_RING_F_EVENT_IDX);
    feature &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
    REGISTER(VIRTIO_MMIO_DRIVER_FEATURES) = feature;

    status |= VIRTIO_CONFIG_S_FEATURES_OK;
    REGISTER(VIRTIO_MMIO_STATUS) = status;
    status |= VIRTIO_CONFIG_S_DRIVER_OK;
    REGISTER(VIRTIO_MMIO_STATUS) = status;
    REGISTER(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PAGE_SIZE;

    REGISTER(VIRTIO_MMIO_QUEUE_SEL) = 0;
    uint32_t max = REGISTER(VIRTIO_MMIO_QUEUE_NUM_MAX);
    if (max == 0)
        panic("virtio disk has no queue 0");
    else if (max < VIRTIO_NUM)
        panic("virtio disk max queue too short");
    REGISTER(VIRTIO_MMIO_QUEUE_NUM) = VIRTIO_NUM;
    memset(disk.pages, 0, sizeof(disk.pages));
    REGISTER(VIRTIO_MMIO_QUEUE_PFN) = (address_t)disk.pages >> PAGE_SHIFT;

    disk.desc = (VRingDesc*)disk.pages;
    disk.avail = (uint16_t*)((char*)disk.desc + VIRTIO_NUM * sizeof(VRingDesc));
    disk.used = (UsedArea*)(disk.pages + PAGE_SIZE);

    for (int i = 0; i < VIRTIO_NUM; ++i)
        disk.free[i] = TRUE;

    __DEBUG_INFO(virtio_disk_init);
}

static int
alloc_desc() {
    for (int i = 0; i < VIRTIO_NUM; ++i)
        if (disk.free[i]) {
            disk.free[i] = FALSE;
            return i;
        }
    return -E_NA_DEV;  // TODO
}

static void
free_decs(int i) {
    if (i >= VIRTIO_NUM)
        panic("virtio_disk_intr 1");
    if (disk.free[i])
        panic("virtio_disk_intr 2");
    disk.desc[i].addr = 0;
    disk.free[i] = TRUE;
    proc_wakeupSpin(&disk.free[0]); // TODO此处似乎有可能遇到锁已获取的情况？
}

static void
free_chain(int i) {
    while (1) {
        free_decs(i); // TODO此处似乎有可能遇到锁已获取的情况？
        if (disk.desc[i].flags & VRING_DESC_F_NEXT)
            i = disk.desc[i].next;
        else
            break;
    }
}

static int
alloc3_desc(int* idx) {
    for (int i = 0; i < 3; ++i) {
        idx[i] = alloc_desc();
        if (idx[i] < 0) {
            for (int j = 0; j < i; ++j)
                free_decs(idx[j]);
            return -E_NA_DEV;  // TODO
        }
    }
    return 0;
}

void
virtio_disk_rw(uint8_t* buf, uint32_t secNo, bool write) {
    spinlock_acquire(&disk.vdisk_lk);
    {
        int idx[3];
        while (alloc3_desc(idx) != 0)
            sleep_spin(&disk.free[0], &disk.vdisk_lk);

        struct virtio_blk_outhdr {
            uint32_t type;
            uint32_t reserved;
            uint64_t sec;
        } buf0;
        if (write)
            buf0.type = VIRTIO_BLK_T_OUT;
        else
            buf0.type = VIRTIO_BLK_T_IN;
        buf0.reserved = 0;
        buf0.sec = secNo;
        disk.desc[idx[0]].addr = (address_t)kvm_trans((address_t)&buf0, getCurrentProc()->kpageTable);  // TODO
        disk.desc[idx[0]].len = sizeof(buf0);
        disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
        disk.desc[idx[0]].next = idx[1];
        // disk.desc[idx[1]].addr = (address_t)buf_temp;
        disk.desc[idx[1]].addr = (address_t)buf;
        disk.desc[idx[1]].len = BSIZE;
        if (write)
            disk.desc[idx[1]].flags = 0;
        else
            disk.desc[idx[1]].flags = VRING_DESC_F_WRITE;

        disk.desc[idx[1]].flags |= VRING_DESC_F_NEXT;
        disk.desc[idx[1]].next = idx[2];

        disk.virt_info[idx[0]].status = 0;
        disk.desc[idx[2]].addr = (address_t)&disk.virt_info[idx[0]].status;
        disk.desc[idx[2]].len = 1;
        disk.desc[idx[2]].flags = VRING_DESC_F_WRITE;
        disk.desc[idx[2]].next = 0;

        disk.virt_info[idx[0]].buf = buf;
        // disk.virt_info[idx[0]].buf = buf_temp;
        disk.virt_info[idx[0]].disk = 1;

        disk.avail[2 + disk.avail[1] % VIRTIO_NUM] = idx[0];
        __sync_synchronize();
        disk.avail[1] += 1;

        REGISTER(VIRTIO_MMIO_QUEUE_NOTIFY) = 0;
        while (disk.virt_info[idx[0]].disk == 1)  // while(bub->disk==1)
            sleep_spin(disk.virt_info[idx[0]].buf, &disk.vdisk_lk);

        disk.virt_info[idx[0]].buf = NULL;
        free_chain(idx[0]); // TODO此处似乎有可能遇到锁已获取的情况？
    }
    spinlock_release(&disk.vdisk_lk);
}

void
virtio_disk_intr() {
    spinlock_acquire(&disk.vdisk_lk);
    while ((disk.used_idx % VIRTIO_NUM) != (disk.used->id % VIRTIO_NUM)) {
        int id = disk.used->elems[disk.used_idx].id;
        if (disk.virt_info[id].status != 0)
            panic("virtio_disk_intr status error");
        disk.virt_info[id].disk = 0;
        proc_wakeupSpin(disk.virt_info[id].buf);  // TODO

        disk.used_idx = (disk.used_idx + 1) % VIRTIO_NUM;
    }
    REGISTER(VIRTIO_MMIO_INTERRUPT_ACK) = REGISTER(VIRTIO_MMIO_INTERRUPT_STATUS) & 0x3;
    spinlock_release(&disk.vdisk_lk);
}

#endif